package tech

import (
	"ssp/set"
)

type Solve struct {
	//techCount map[string]int
	techiques []Techique
}

func (s *Solve) prepare() {
	s.techiques = []Techique{
		s.LastOne,
		s.NakedSingle,
		s.HiddenSingleBox,
		s.HiddenSingleRow,
		s.HiddenSingleCol,
		s.NakedPair,
		s.HiddenPair,
		s.NakedTriple,
		s.HiddenTriple,
		s.NakedQuadruple,
		s.HiddenQuadruple,
		s.Pointing,
		s.ClaimingRow,
		s.ClaimingCol,
		s.CounterDR1,
		s.CounterDR2,
		s.CounterDR3,
		s.CounterDR4,
		//s.AvoidRectangleType1,
		//s.AvoidRectangleType2A,
		//s.AvoidRectangleType2B,
		//s.AvoidRectangleType3A,
		//s.AvoidRectangleType4C,
		//s.UniqueRectangle1,
		//s.UniqueRectangle2,
		//s.UniqueRectangle3,
		//s.UniqueRectangle4A,
		//s.UniqueRectangle4A,
		//s.UniqueRectangle4B,
		//s.UniqueRectangle4C,
		//s.UniqueRectangle4D,
		//s.UniqueRectangle4E,
		//s.UniqueRectangle4F,
		//s.UniqueRectangle4G,
		//s.UniqueRectangle4H,
		s.SimpleColoring,
		s.XWing,
		s.Swordfish,
		s.Jellyfish,
		s.FinnedXWing,
		s.FinnedSwordfish,
		s.FinnedJellyfish,
		s.ChuteRemotePairs,
		s.WRectangle,
		s.WWing,
		s.RectangleElimination,
		//s.SKLoop,
		s.XYWing,
		s.XYZWing,
		s.WXYZWing,
		s.VWXYZWing,
		s.JuniorExocet,
		s.OneBaseJuniorExocet,
		s.Medusa,
		s.NNWing,
		s.XCycle2,
		s.XCycle3,
		s.XYCycle3,
		s.GroupXCycle3,
		s.AIC2,
		s.AIC3,
		s.AICWithGroup3,
		s.AICWithALS3,
		s.TripleCellForcingChain,
		s.TripleHouseForcingChain,
		s.QuadCellForcingChain,
		s.QuadHouseForcingChain,
	}
}

func (s *Solve) Guess(g *Grid) (used bool, result *Result) {
	if g.solvedCount == 81 {
		return
	}
	guess := g.guessCell()

	if s.Backtrack(g) < 1 {
		return
	}
	guess.value = g.cells[guess.rowNum][guess.colNum].value

	result = new(Result)
	result.setChosen(guess.value, &guess)
	result.setStrategyName("Guess")
	used = true
	return
}

func (s *Solve) backtrack(g Grid, results []Grid) []Grid {
	g.Init()
	g.Clearup()
	if !g.filledCorrenctly() {
		return []Grid{}
	}

	g.basicTech()
	if g.solvedCount == 81 {
		results = append(results, g)
		return results
	}

	if len(results) > 100 {
		return results
	}

	cell := g.guessCell()
	for _, cand := range cell.cands.GetList() {
		x := g
		x.Init()
		x.Clearup()
		x.solveOneCell(cell.rowNum, cell.colNum, toValue(cand))
		r := s.backtrack(x, results)
		if len(r) > 0 {
			results = r
			if len(results) > 100 {
				return results
			}
		}
	}
	return results
}

func (g *Grid) solveOneCell(rowNum int, colNum int, value int) {
	if g.cells[rowNum][colNum].solved() {
		return
	}
	g.cells[rowNum][colNum].setValue(value)
	g.rows[rowNum].solvedCount++
	g.cols[colNum].solvedCount++
	g.boxes[g.cells[rowNum][colNum].boxNum].solvedCount++
	g.solvedCount++
	clearupHouse(g.rows[rowNum], colNum)
	clearupHouse(g.cols[colNum], rowNum)
	clearupHouse(g.boxes[g.cells[rowNum][colNum].boxNum], g.cells[rowNum][colNum].cellNum)
}

func (s *Solve) Backtrack(g *Grid) (n int) {
	if g.solvedCount < 17 {
		return -1
	}
	stack := []Grid{}
	stack = s.backtrack(*g, stack)
	n = len(stack)
	switch {
	case n == 0:
		return
	case n == 1:
		*g = stack[0]
		return
	default:
		/*
			for _, r := range results[:3] {
				r.PrintGrid()
				fmt.Println("--------------------")
			}
		*/

		*g = stack[0]
		return n
	}
}

func (g *Grid) guessCell() (cell Cell) {
	cells := g.selectMinUnsolved()
	if len(cells) == 0 {
		return
	}
	cell = *cells[0]
	return
}

func filledCorrectlyHouse(house House) bool {
	values := set.NewSet()
	for _, cell := range house.cells {
		if cell.solved() {
			values.Add(toCand(cell.value))
		} else {
			if cell.cands.Count() == 0 {
				return false
			}
		}
	}
	return values.Count() == house.solvedCount
}

func (g *Grid) filledCorrenctly() (ok bool) {
	for _, row := range g.rows {
		if !filledCorrectlyHouse(row) {
			return
		}
	}
	for _, col := range g.cols {
		if !filledCorrectlyHouse(col) {
			return
		}
	}
	for _, box := range g.boxes {
		if !filledCorrectlyHouse(box) {
			return
		}
	}
	ok = true
	return
}

func (g *Grid) minCandidateCount() int {
	count := 9
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if !cell.solved() && cell.cands.Count() < count {
				count = cell.cands.Count()
			}
		}
	}
	return count
}

func (g *Grid) selectMinUnsolved() []*Cell {
	minc := g.minCandidateCount()
	cells := []*Cell{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() || cell.cands.Count() > minc {
				continue
			}
			cells = append(cells, cell)
		}
	}
	return cells
}

func (g *Grid) removeHiddenSingleHouse(house House) (ok bool) {
	for cand := 0; cand < 9; cand++ {
		var solved *Cell
		count := 0
		for _, cell := range house.cells {
			if cell.solved() {
				continue
			}
			if cell.cands.Has(cand) {
				count++
				solved = cell
			}
		}
		if count != 1 {
			continue
		}
		value := toValue(cand)
		g.solveOneCell(solved.rowNum, solved.colNum, value)
		ok = true
	}

	return
}

func (g *Grid) removeHiddenSingle() (used bool) {
	for _, box := range g.boxes {
		if g.removeHiddenSingleHouse(box) {
			used = true
		}
	}

	for _, row := range g.rows {
		if g.removeHiddenSingleHouse(row) {
			used = true
		}
	}

	for _, col := range g.cols {
		if g.removeHiddenSingleHouse(col) {
			used = true
		}
	}
	return
}

func (g *Grid) removeNakedSingle() (used bool) {
	for rowNum, row := range g.rows {
		for colNum, cell := range row.cells {
			if cell.solved() || cell.cands.Count() != 1 {
				continue
			}
			value := toValue(cell.cands.GetList()[0])
			g.solveOneCell(rowNum, colNum, value)
			used = true
		}
	}
	return
}

func (g *Grid) basicTech() (used bool) {
	for {
		used = false
		if g.removeNakedSingle() {
			used = true
		}
		if g.removeHiddenSingle() {
			used = true
		}
		if !used {
			break
		}
	}
	return
}

func (s *Solve) Step(g *Grid) (used bool, results []*Result) {
	OUTPUTRESULT = true
	EXHAUSTIVE = true
	CHANGE = false

	//fmt.Println("step", g.solvedCount)
	if g.solvedCount == 81 {
		used = true
		return
	}
	//preGrid = *g
	for _, techique := range s.techiques {
		//name := runtime.FuncForPC(reflect.ValueOf(techique).Pointer()).Name()
		//fmt.Println("Trying technique:", name[strings.LastIndex(name, ".")+1:])

		used, results = techique(g)

		if used {
			return
		}
	}
	used, result := s.Guess(g)
	if !used {
		return
	}
	results = append(results, result)
	used = true
	return
}

func NewSolve() *Solve {
	s := &Solve{}
	s.prepare()
	return s
}

func (s *Solve) Make(g *Grid) (finished bool, results []*Result) {
	OUTPUTRESULT = true
	EXHAUSTIVE = false
	CHANGE = true

	used := false
	n := 0
	for !g.Solved() {
		for _, t := range s.techiques {
			ok, rs := t(g)
			//fmt.Println("Technique", ti, ok, len(rs))
			if ok && len(rs) > 0 {
				used = true
				results = append(results, rs[0])
				n++
				//fmt.Println(n, rs[0].StrategyName)
				break
			}
		}

		if !used {
			//fmt.Println("No technique can be used.")
			break
		}
		used = false
		if n > 300 {
			break
		}
	}
	finished = g.Solved()
	//if finished {
	//	PrintResults(results)
	//}
	return
}
